<?php


namespace services;

use app\services\message\wechat\MessageService;
use app\services\pay\PayNotifyServices;
use app\services\pay\PayService;
use Application;
use EasyWeChat\Factory;
use EasyWeChat\Work\Server\Guard;
use exceptions\AdminException;
use exceptions\ApiException;
use think\exception\ValidateException;
use think\facade\Cache;
use think\Response;
use utils\Hook;

class WechatService
{
    /**
     * @var Application
     */
    protected static $instance;

    public static function options(array $options = [])
    {
        $wechat  = SystemConfigService::more(['wechat_appid', 'wechat_appsecret', 'wechat_token', 'wechat_encodingaeskey', 'wechat_encode']);
        $payment = SystemConfigService::more(['pay_weixin_mchid', 'pay_weixin_client_cert', 'pay_weixin_client_key', 'pay_weixin_key', 'pay_weixin_open']);
        $config  = [
            'app_id' => isset($wechat['wechat_appid']) ? trim($wechat['wechat_appid']) : '',
            'secret' => isset($wechat['wechat_appsecret']) ? trim($wechat['wechat_appsecret']) : '',
            'token'  => isset($wechat['wechat_token']) ? trim($wechat['wechat_token']) : '',
            'guzzle' => [
                'timeout' => 10.0, // 超时时间（秒）
                'verify'  => false
            ],
        ];
        if (isset($wechat['wechat_encode']) && (int)$wechat['wechat_encode'] > 0 && isset($wechat['wechat_encodingaeskey']) && !empty($wechat['wechat_encodingaeskey']))
            $config['aes_key'] = $wechat['wechat_encodingaeskey'];
        if (isset($payment['pay_weixin_open']) && $payment['pay_weixin_open'] == 1) {
            $config['payment'] = [
                'merchant_id' => trim($payment['pay_weixin_mchid']),
                'key'         => trim($payment['pay_weixin_key']),
                'cert_path'   => realpath('.' . $payment['pay_weixin_client_cert']),
                'key_path'    => realpath('.' . $payment['pay_weixin_client_key']),
                'notify_url'  => trim(SystemConfigService::get('site_url')) . '/api/wechat/notify'
            ];
        }
        return $config;
    }

    /**
     * Description:
     *
     * @param       $app
     * @param array $config
     *
     * @return \EasyWeChat\OfficialAccount\Application|\EasyWeChat\MiniProgram\Application|\EasyWeChat\Payment\Application|\EasyWeChat\Work\Server\Guard
     */
    public static function application($app, array $config = [])
    {
        return Factory::$app(self::options($config));
    }

    /**
     * Description: 微信小程序实例
     *
     * @return \EasyWeChat\MiniProgram\Application
     */
    public static function miniprogram()
    {
        return self::application('mini_program');
    }

    /**
     * Description:支付实例
     *
     * @return \EasyWeChat\Payment\Application
     */
    public static function paymentService()
    {
        return self::application('payment');
    }

    public static function officialService()
    {
        $wechat = SystemConfigService::more([
            'site_url',
            // 小程序appid
            'wechat_appid',
            // 小程序secret
            'wechat_appsecret',
        ]);
        return self::application('officialAccount', $wechat);
    }

    /**
     * Description: 消息模板实例
     *
     * @return \EasyWeChat\MiniProgram\TemplateMessage\Client
     */
    public static function templateNnoticeService()
    {
        return self::officialService()->template_message;
    }

    /**
     * Description: 订阅模板消息实例
     *
     * @return \EasyWeChat\MiniProgram\SubscribeMessage\Client
     */
    public static function subscribeNoticeService()
    {
        return self::miniprogram()->subscribe_message;
    }

    /**
     * Description: 获取用户unionid
     *
     * @param string $openId
     *
     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
     */
    public static function getUserUnionId(string $openId)
    {
        return self::officialService()->user->get($openId);
    }

    public static function getAuthByJsCode(string $code)
    {
        return self::miniprogram()->auth->session($code);
    }

    public static function decryptAuthData(string $session, string $iv, string $encryptedData)
    {
        return self::miniprogram()->encryptor->decryptData($session, $iv, $encryptedData);
    }

    public static function serve(): Response
    {
        $wechat = self::application('server');
        self::hook($wechat);
        $response = $server->serve();
        return response($response->getContent());
    }

    /**
     * 监听行为
     *
     * @param Guard $server
     */
    private static function hook($server)
    {
        /** @var MessageService $messageService */
        $messageService = app()->make(MessageService::class);
        /** @var1 WechatReplyServices $wechatReplyService */
        // $wechatReplyService = app()->make(WechatReplyServices::class);
        $server->push(function ($message) use ($messageService) {
            /** @var1 WechatMessageService $wechatMessage */
            // $wechatMessage = app()->make(WechatMessageServices::class);
            // $wechatMessage->wechatMessageBefore($message);
            switch ($message['MsgType']) {
                case 'event':
                    switch (strtolower($message['Event'])) {
                        case 'subscribe':
                            $response = $messageService->wechatEventSubscribe($message);
                            break;
                        case 'unsubscribe':
                            $messageService->wechatEventUnsubscribe($message);
                            break;
                        case 'scan':
                            $response = $messageService->wechatEventScan($message);
                            break;
                        case 'location':
                            $response = $messageService->wechatEventLocation($message);
                            break;
                        case 'click':
                            // $response = $wechatReplyService->reply($message->EventKey);
                            break;
                        case 'view':
                            $response = $messageService->wechatEventView($message);
                            break;
                    }
                    break;
                case 'text':
                    $response = $wechatReplyService->reply($message->Content, $message->FromUserName);
                    break;
                case 'image':
                    $response = $messageService->wechatMessageImage($message);
                    break;
                case 'voice':
                    $response = $messageService->wechatMessageVoice($message);
                    break;
                case 'video':
                    $response = $messageService->wechatMessageVideo($message);
                    break;
                case 'location':
                    $response = $messageService->wechatMessageLocation($message);
                    break;
                case 'link':
                    $response = $messageService->wechatMessageLink($message);
                    break;
                // ... 其它消息
                default:
                    $response = $messageService->wechatMessageOther($message);
                    break;
            }

            return $response ?? false;
        });
    }


    /**
     * Description: 发送小程序模版消息
     *
     * @param       $toUser
     * @param       $templateId
     * @param array $data
     *
     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public static function sendTemplate(
        $toUser,
        $templateId,
        array $data,
        string $url = ''
    )
    {
        return self::templateNnoticeService()->send([
            "touser"      => $toUser,
            "template_id" => $templateId,
            "data"        => $data,
            'miniprogram' => [
                'appid'    => sysconfig('routine_appid'),
                'pagepath' => $url,
            ],
        ]);
    }

    /**
     * Description: 发送订阅消息
     *
     * @param string $toUser     接收者（用户）的 openid
     * @param string $templateId 所需下发的订阅模板id
     * @param array  $data       模板内容，格式形如 { "key1": { "value": any }, "key2": { "value": any } }
     * @param string $link       点击模板卡片后的跳转页面，仅限本小程序内的页面。支持带参数,（示例index?foo=bar）。该字段不填则模板无跳转。
     *
     * @return mixed
     */
    public static function sendSubscribeTemplate(string $toUser, string $templateId, array $data, string $link = '')
    {
        return self::subscribeNoticeService()->send([
            'template_id' => $templateId,
            'touser'      => $toUser,
            'page'        => $link,
            'data'        => $data,
        ]);
    }


    public static function downloadBill($day, $type = 'ALL')
    {
        //        $payment = self::paymentService();
        //        $merchant = $payment->getMerchant();
        //        $params = [
        //            'appid' => $merchant->app_id,
        //            'bill_date'=>$day,
        //            'bill_type'=>strtoupper($type),
        //            'mch_id'=> $merchant->merchant_id,
        //            'nonce_str' => uniqid()
        //        ];
        //        $params['sign'] = \EasyWeChat\Payment\generate_sign($params, $merchant->key, 'md5');
        //        $xml = XML::build($params);
        //        dump(self::paymentService()->downloadBill($day)->getContents());
        //        dump($payment->getHttp()->request('https://api.mch.weixin.qq.com/pay/downloadbill','POST',[
        //            'body' => $xml,
        //            'stream'=>true
        //        ])->getBody()->getContents());
    }

    /**
     * 企业付款到零钱
     *
     * @param string $openid  openid
     * @param string $orderId 订单号
     * @param string $amount  金额
     * @param string $desc    说明
     */
    public static function merchantPay(string $openid, string $orderId, string $amount, string $desc)
    {
        $options = self::options();
        if (!isset($options['payment']['cert_path'])) {
            throw new ValidateException('企业微信支付到零钱需要支付证书，检测到您没有上传！');
        }
        if (!$options['payment']['cert_path']) {
            throw new ValidateException('企业微信支付到零钱需要支付证书，检测到您没有上传！');
        }
        $merchantPayData = [
            'partner_trade_no' => $orderId,                       //随机字符串作为订单号，跟红包和支付一个概念。
            'openid'           => $openid,                        //收款人的openid
            'check_name'       => 'NO_CHECK',                     //文档中有三种校验实名的方法 NO_CHECK OPTION_CHECK FORCE_CHECK
            'amount'           => (int)bcmul($amount, '100', 0),  //单位为分
            'desc'             => $desc,
            'spbill_create_ip' => request()->ip(),  //发起交易的IP地址
        ];
        $result          = self::paymentService()->merchant_pay->send($merchantPayData);
        if ($result->return_code == 'SUCCESS' && $result->result_code != 'FAIL') {
            return true;
        } else {
            throw new ValidateException(($result->return_msg ?? '支付失败') . ':' . ($result->err_code_des ?? '发起企业支付到零钱失败'));
        }
    }

    protected static function paymentOrder($openid, $out_trade_no, $total_fee, $attach, $body, $detail = '', $trade_type = 'JSAPI', $options = [])
    {
        $total_fee = bcmul($total_fee, 100, 0);
        // $total_fee = 1;
        $order = array_merge(
            compact('openid', 'out_trade_no', 'total_fee', 'attach', 'body', 'detail', 'trade_type'),
            $options
        );
        return $order;
    }

    /**
     * 获得下单ID
     *
     * @param        $openid
     * @param        $out_trade_no
     * @param        $total_fee
     * @param        $attach
     * @param        $body
     * @param string $detail
     * @param string $trade_type
     * @param array  $options
     *
     * @return mixed
     */
    public static function paymentPrepare($openid, $out_trade_no, $total_fee, $attach, $body, $detail = '', $trade_type = 'JSAPI', $options = [])
    {
        $key    = 'pay_' . $out_trade_no;
        $result = Cache::get($key);
        if ($result) {
            return $result;
        }
        $order  = self::paymentOrder($openid, $out_trade_no, $total_fee, $attach, $body, $detail, $trade_type, $options);
        $result = self::paymentService()->order->unify($order);
        if ($result->return_code == 'SUCCESS' && $result->result_code == 'SUCCESS') {
            Cache::set($key, $result, 7000);
            return $result;
        }
        if ($result->return_code == 'FAIL') {
            throw new ApiException('微信支付错误返回：' . $result->return_msg);
        } elseif (isset($result->err_code)) {
            throw new ApiException('微信支付错误返回：' . $result->err_code_des);
        } else {
            throw new ApiException('没有获取微信支付的预支付ID，请重新发起支付!');
        }
    }

    /**
     * Description: 获得jsSdk支付参数
     *
     * @param        $openid
     * @param        $out_trade_no
     * @param        $total_fee
     * @param        $attach
     * @param        $body
     * @param string $detail
     * @param array  $options
     *
     * @return array|string
     */
    public static function jsPay($openid, $out_trade_no, $total_fee, $attach, $body, $detail = '', $options = [])
    {
        return self::paymentService()->jssdk->bridgeConfig(
            self::paymentPrepare($openid, $out_trade_no, $total_fee, $attach, $body, $detail, 'JSAPI', $options)
            , false);
    }

    /**
     * 获得native支付参数
     *
     * @param        $openid
     * @param        $out_trade_no
     * @param        $total_fee
     * @param        $attach
     * @param        $body
     * @param string $detail
     * @param string $trade_type
     * @param array  $options
     *
     * @return array|string
     */
    public static function nativePay($openid, $out_trade_no, $total_fee, $attach, $body, $detail = '', $trade_type = 'NATIVE', $options = [])
    {
        $data = self::paymentPrepare($openid, $out_trade_no, $total_fee, $attach, $body, $detail, $trade_type, $options);
        if ($data) {
            $res['code_url'] = $data['code_url'];
            $res['invalid']  = time() + 60;
            $res['logo']     = sysconfig('wap_login_logo');
        } else $res = [];
        return $res;
    }


    /**
     * 使用商户订单号退款
     *
     * @param        $orderNo
     * @param        $refundNo
     * @param        $totalFee
     * @param null   $refundFee
     * @param null   $opUserId
     * @param string $refundReason
     * @param string $type
     * @param string $refundAccount
     */
    public static function refund($orderNo, $refundNo, $totalFee, $refundFee = null, $opUserId = null, $refundReason = '', $type = 'out_trade_no', $refundAccount = 'REFUND_SOURCE_UNSETTLED_FUNDS')
    {
        $totalFee  = floatval($totalFee);
        $refundFee = floatval($refundFee);
        if ($type == 'out_trade_no') {
            return self::paymentService()->refund($orderNo, $refundNo, $totalFee, $refundFee, $opUserId, $type, $refundAccount, $refundReason);
        } else {
            return self::paymentService()->refundByTransactionId($orderNo, $refundNo, $totalFee, $refundFee, $opUserId, $refundAccount, $refundReason);
        }
    }

    public static function payOrderRefund($orderNo, array $opt)
    {
        if (!isset($opt['pay_price']))
            throw new AdminException('缺少pay_price');
        $totalFee     = floatval(bcmul($opt['pay_price'], 100, 0));
        $refundFee    = isset($opt['refund_price']) ? floatval(bcmul($opt['refund_price'], 100, 0)) : null;
        $refundReason = isset($opt['desc']) ? $opt['desc'] : '';
        $refundNo     = isset($opt['refund_id']) ? $opt['refund_id'] : $orderNo;
        $opUserId     = isset($opt['op_user_id']) ? $opt['op_user_id'] : null;
        $type         = isset($opt['type']) ? $opt['type'] : 'out_trade_no';
        /*仅针对老资金流商户使用
        REFUND_SOURCE_UNSETTLED_FUNDS---未结算资金退款（默认使用未结算资金退款）
        REFUND_SOURCE_RECHARGE_FUNDS---可用余额退款*/
        $refundAccount = isset($opt['refund_account']) ? $opt['refund_account'] : 'REFUND_SOURCE_UNSETTLED_FUNDS';
        try {
            $res = (self::refund($orderNo, $refundNo, $totalFee, $refundFee, $opUserId, $refundReason, $type, $refundAccount));
            if ($res->return_code == 'FAIL')
                throw new AdminException('退款失败:' . $res->return_msg);
            if (isset($res->err_code))
                throw new AdminException('退款失败:' . $res->err_code_des);
        } catch (\Exception $e) {
            throw new AdminException($e->getMessage());
        }
        return true;
    }

    /**
     * Description: 微信支付成功回调接口
     *
     * @return mixed
     */
    public static function handleNotify()
    {
        return self::paymentService()->handlePaidNotify(function ($message, $fail) {
            if ($message['return_code'] == 'SUCCESS') {
                return (new Hook(PayNotifyServices::class, 'wechat'))
                    ->listen($message['attach'], $message['out_trade_no'], $message['transaction_id']);
            }
            return $fail('通信失败，请稍后再通知我');
        });
    }

    /**
     * jsSdk
     * @return \EasyWeChat\Js\Js
     */
    public static function jsService()
    {
        return self::application()->js;
    }

    /**
     * 获取js的SDK
     *
     * @param string $url
     *
     * @return array|string
     */
    public static function jsSdk($url = '')
    {
        $apiList   = ['openAddress', 'updateTimelineShareData', 'updateAppMessageShareData', 'onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ', 'onMenuShareWeibo', 'onMenuShareQZone', 'startRecord', 'stopRecord', 'onVoiceRecordEnd', 'playVoice', 'pauseVoice', 'stopVoice', 'onVoicePlayEnd', 'uploadVoice', 'downloadVoice', 'chooseImage', 'previewImage', 'uploadImage', 'downloadImage', 'translateVoice', 'getNetworkType', 'openLocation', 'getLocation', 'hideOptionMenu', 'showOptionMenu', 'hideMenuItems', 'showMenuItems', 'hideAllNonBaseMenuItem', 'showAllNonBaseMenuItem', 'closeWindow', 'scanQRCode', 'chooseWXPay', 'openProductSpecificView', 'addCard', 'chooseCard', 'openCard'];
        $jsService = self::jsService();
        if ($url)
            $jsService->setUrl($url);
        try {
            return $jsService->config($apiList);
        } catch (\Exception $e) {
            return '{}';
        }

    }


    /**
     * 回复文本消息
     *
     * @param string $content 文本内容
     *
     * @return Text
     */
    public static function textMessage($content)
    {
        return new Text(compact('content'));
    }

    /**
     * 回复图片消息
     *
     * @param string $media_id 媒体资源 ID
     *
     * @return Image
     */
    public static function imageMessage($media_id)
    {
        return new Image(compact('media_id'));
    }

    /**
     * 回复视频消息
     *
     * @param string $media_id       媒体资源 ID
     * @param string $title          标题
     * @param string $description    描述
     * @param null   $thumb_media_id 封面资源 ID
     *
     * @return Video
     */
    public static function videoMessage($media_id, $title = '', $description = '...', $thumb_media_id = null)
    {
        return new Video(compact('media_id', 'title', 'description', 'thumb_media_id'));
    }

    /**
     * 回复声音消息
     *
     * @param string $media_id 媒体资源 ID
     *
     * @return Voice
     */
    public static function voiceMessage($media_id)
    {
        return new Voice(compact('media_id'));
    }

    /**
     * 回复图文消息
     *
     * @param string|array $title       标题
     * @param string       $description 描述
     * @param string       $url         URL
     * @param string       $image       图片链接
     */
    public static function newsMessage($title, $description = '...', $url = '', $image = '')
    {
        if (is_array($title)) {
            if (isset($title[0]) && is_array($title[0])) {
                $newsList = [];
                foreach ($title as $news) {
                    $newsList[] = self::newsMessage($news);
                }
                return $newsList;
            } else {
                $data = $title;
            }
        } else {
            $data = compact('title', 'description', 'url', 'image');
        }
        return new News($data);
    }

    /**
     * 回复文章消息
     *
     * @param string|array $title                 标题
     * @param string       $thumb_media_id        图文消息的封面图片素材id（必须是永久 media_ID）
     * @param string       $source_url            图文消息的原文地址，即点击“阅读原文”后的URL
     * @param string       $content               图文消息的具体内容，支持HTML标签，必须少于2万字符，小于1M，且此处会去除JS
     * @param string       $author                作者
     * @param string       $digest                图文消息的摘要，仅有单图文消息才有摘要，多图文此处为空
     * @param int          $show_cover_pic        是否显示封面，0为false，即不显示，1为true，即显示
     * @param int          $need_open_comment     是否打开评论，0不打开，1打开
     * @param int          $only_fans_can_comment 是否粉丝才可评论，0所有人可评论，1粉丝才可评论
     *
     * @return Article
     */
    public static function articleMessage($title, $thumb_media_id, $source_url, $content = '', $author = '', $digest = '', $show_cover_pic = 0, $need_open_comment = 0, $only_fans_can_comment = 1)
    {
        $data = is_array($title) ? $title : compact('title', 'thumb_media_id', 'source_url', 'content', 'author', 'digest', 'show_cover_pic', 'need_open_comment', 'only_fans_can_comment');
        return new Article($data);
    }

    /**
     * 回复素材消息
     *
     * @param string $type     [mpnews、 mpvideo、voice、image]
     * @param string $media_id 素材 ID
     *
     * @return Material
     */
    public static function materialMessage($type, $media_id)
    {
        return new Material($type, $media_id);
    }

    /**
     * 作为客服消息发送
     *
     * @param $to
     * @param $message
     *
     * @return bool
     */
    public static function staffTo($to, $message)
    {
        $staff = self::staffService();
        $staff = is_callable($message) ? $staff->message($message()) : $staff->message($message);
        $res   = $staff->to($to)->send();
        return $res;
    }

    /**
     * Description: 获得用户信息
     *
     * @param $openid
     *
     * @return mixed
     */
    public static function getUserInfo($openid)
    {
        $userService = self::userService();
        $userInfo    = [];
        try {
            if (is_array($openid)) {
                $res = $userService->batchGet($openid);
                if (isset($res['user_info_list'])) {
                    $userInfo = $res['user_info_list'];
                } else {
                    throw new ValidateException($res['errmsg'] ?? '获取微信粉丝信息失败');
                }
            } else {
                $userInfo = $userService->get($openid);
            }
        } catch (\Throwable $e) {
            throw new ValidateException($e->getMessage());
        }
        return $userInfo;
    }


    /**
     * 获取用户列表
     *
     * @param null $next_openid
     *
     * @return array
     */
    public static function getUsersList($next_openid = null)
    {
        $userService = self::userService();
        $list        = [];
        try {
            $res                 = $userService->lists($next_openid);
            $list['data']        = $res['data']['openid'] ?? [];
            $list['next_openid'] = $res['next_openid'] ?? null;
            return $list;
        } catch (\Exception $e) {
            throw new ValidateException($e->getMessage());
        }
        return $list;
    }
}
